unit GLGUIComposite;

interface

{$I GLScene.inc}

uses
  Classes,
  GLRenderContextInfo,
  GLState,
  GLColor,
  VectorGeometry,
  GLCrossPlatform,
  GLSGenerics,
  GLGUIBase,
  GLGUI2DSprite,
  GLGUIQuad,
  GLGUISkin,
  GLGUICompositeSkin,
  GLGUIState,
  GLGUIEventManager,
  GLGUIEvent,
  GLGUITokens,
  GLGUISkinManager,
  GLCoordinates;

type
  TGLGUIComposite = class(TGLGUI2DSprite)
  protected
    FPartsCount: Byte;
    FPartsInfo: TGLGUIParts;
    FLeftBorderWidth: Single;
    FRightBorderWidth: Single;
    FTopBorderHeight: Single;
    FBottomBorderHeight: Single;
    FHeightOrigin: Single;
    FWidthOrigin: Single;
    FSkin: TGLGUICompositeSkin;
    FBordersScaleX, FBordersScaleY: Single;
    FScaleBorders: Boolean;
    FNeedResize: Boolean;
    FParts: Array[0..8] of TGLGUIQuad;
    function GetWidth(): Single; override;
    procedure SetWidth(const AWidth: Single); override;
    function GetHeight(): Single; override;
    procedure SetHeight(const AHeight: Single); override;
    function GetScissorLeft: Integer; override;
    function GetScissorBottom: Integer; override;
    function GetScissorHeight: Integer; override;
    function GetScissorWidth: Integer; override;
    procedure SetAlpha(const AAlpha: Single); override;
    procedure SetColor(AColor: TGLColor); override;
    procedure SetSkinName(const ASkinName: String); override;
    procedure SetSkin(const ASkin: TGLGUICompositeSkin); virtual;
    procedure AddPart(const APart: TGLGUIPart; const ASkin: TGLGUIBasicSkin); virtual;
    function GetBoreders(): TVector;
    procedure SetBordersSize(const ATop, ABottom, ALeft, ARight : Single); overload; virtual;
    procedure SetBordersSize(const ASize: TVector); overload; virtual;
    procedure SetState(const AState: TGLGUIState); override;
    procedure DoOnResize();
  public
    constructor Create(AOwner: TComponent); override;
    procedure DoRender(var ARci: TRenderContextInfo;
      ARenderSelf, ARenderChildren: Boolean); override;
    procedure UpdatePartsColor(Sender: TObject); virtual;
    procedure NotifyGUIChange(Sender: TObject = nil); override;
    procedure DispatchEvent(AEvent: TGLGUIEvent; AExternal: Boolean = True); override;
    destructor Destroy; override;

    property Borders: TVector read GetBoreders;
  end;

  TGLGUICompositesList = GList<TGLGUIComposite>;

implementation
uses
  GLGUILayer;
type
  TGLGUIQuadAccessProvider = class(TGLGUIQuad);

constructor TGLGUIComposite.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FWidth := 16;
  FHeight := 16;
  FMinWidth := FWidth;
  FMinHeight := FHeight;
  FScaleBorders := False;
  FAlpha := 1.0;
  FNeedResize := True;
  FColor.OnNotifyChange := UpdatePartsColor;
end;

procedure TGLGUIComposite.DoRender(var ARci: TRenderContextInfo;
  ARenderSelf, ARenderChildren: Boolean);
var
  I: Integer;
begin
  if ARenderSelf then
  begin
    for I := 0 to 8 do
    if TGLGUIPart(i) in FPartsInfo then
      FParts[i].DoRender(ARci, Visible, False);
  end;

  if ARenderChildren then
    Self.RenderChildren(FPartsCount, Count - 1, ARci);
end;

procedure TGLGUIComposite.DoOnResize;
var
  TmpPart: TGLGUIQuad;
  vTopBorderHeight: Single;
  vBottomBorderHeight: Single;
  vLeftBorderWidth: Single;
  vRightBorderWidth: Single;
begin
  if FScaleBorders then
  begin
    FBordersScaleX := 1;
    FBordersScaleY := 1;
  end
  else
  begin
    FBordersScaleX := 1 / Self.AbsoluteScale[0];
    FBordersScaleY := 1 / Self.AbsoluteScale[1];
  end;

  vTopBorderHeight := FTopBorderHeight * FBordersScaleY;
  vBottomBorderHeight := FBottomBorderHeight * FBordersScaleY;
  vLeftBorderWidth := FLeftBorderWidth * FBordersScaleX;
  vRightBorderWidth := FRightBorderWidth * FBordersScaleX;

  if (FWidthOrigin < FMinWidth) then
    FWidthOrigin := FMinWidth;
  FWidth := FWidthOrigin - vLeftBorderWidth - vRightBorderWidth;
  if (FHeightOrigin < FMinHeight) then
    FHeightOrigin := FMinHeight;
  FHeight := FHeightOrigin - vTopBorderHeight - vBottomBorderHeight;

  if GLGUIPartCenter in FPartsInfo then
  begin
    TmpPart := FParts[Integer(GLGUIPartCenter)];
    TmpPart.Width := FWidth;
    TmpPart.Height := FHeight;
    TmpPart.Position.Y := (vTopBorderHeight - vBottomBorderHeight)/2;
    TmpPart.Position.X := (vLeftBorderWidth - vRightBorderWidth)/2;
  end;
  if GLGUIPartTop in FPartsInfo then
  begin
    TmpPart := FParts[Integer(GLGUIPartTop)];
    TmpPart.Position.Y := - FHeight / 2 - vBottomBorderHeight/2;
    TmpPart.Position.X := (vLeftBorderWidth - vRightBorderWidth)/2;
    TmpPart.Width := FWidth;
    TmpPart.Height := vTopBorderHeight;
  end;
  if GLGUIPartBottom in FPartsInfo then
  begin
    TmpPart := FParts[Integer(GLGUIPartBottom)];
    TmpPart.Position.Y := FHeight / 2 + vTopBorderHeight / 2;
    TmpPart.Position.X := (vLeftBorderWidth - vRightBorderWidth)/2;
    TmpPart.Width := FWidth;
    TmpPart.Height := vBottomBorderHeight;
  end;
  if GLGUIPartLeft in FPartsInfo then
  begin
    TmpPart := FParts[Integer(GLGUIPartLeft)];
    TmpPart.Position.X := -FWidth / 2 - vRightBorderWidth / 2;
    TmpPart.Width := vLeftBorderWidth;
    TmpPart.Height := FHeight;
    TmpPart.Position.Y := (vTopBorderHeight - vBottomBorderHeight)/2;
  end;
  if GLGUIPartRight in FPartsInfo then
  begin
    TmpPart := FParts[Integer(GLGUIPartRight)];
    TmpPart.Position.X := FWidth / 2 + vLeftBorderWidth / 2;
    TmpPart.Width := vRightBorderWidth;
    TmpPart.Height := FHeight;
    TmpPart.Position.Y := (vTopBorderHeight - vBottomBorderHeight)/2;
  end;
  if GLGUIPartTopLeft in FPartsInfo then
  begin
    TmpPart := FParts[Integer(GLGUIPartTopLeft)];
    TmpPart.Position.X := -FWidth / 2 - vRightBorderWidth / 2;
    TmpPart.Position.Y := -FHeight / 2 - vBottomBorderHeight / 2;
    TmpPart.Width := vLeftBorderWidth;
    TmpPart.Height := vTopBorderHeight;
  end
  else if GLGUIPartLeft in FPartsInfo then
  begin
    FParts[Integer(GLGUIPartLeft)].Height := FParts[Integer(GLGUIPartLeft)].Height + vTopBorderHeight;
    FParts[Integer(GLGUIPartLeft)].Position.Y := FParts[Integer(GLGUIPartLeft)].Position.Y - vTopBorderHeight/2;
  end;
  if GLGUIPartTopRight in FPartsInfo then
  begin
    TmpPart := FParts[Integer(GLGUIPartTopRight)];
    TmpPart.Position.X := FWidth / 2 + vLeftBorderWidth / 2;
    TmpPart.Position.Y := -FHeight / 2 - vBottomBorderHeight / 2;
    TmpPart.Width := vRightBorderWidth;
    TmpPart.Height := vTopBorderHeight;
  end
  else if GLGUIPartRight in FPartsInfo then
  begin
    FParts[Integer(GLGUIPartRight)].Height := FParts[Integer(GLGUIPartRight)].Height + vTopBorderHeight;
    FParts[Integer(GLGUIPartRight)].Position.Y := FParts[Integer(GLGUIPartRight)].Position.Y - vTopBorderHeight/2;
  end;
  if GLGUIPartBottomLeft in FPartsInfo then
  begin
    TmpPart := FParts[Integer(GLGUIPartBottomLeft)];
    TmpPart.Position.X := - FWidth / 2 - vRightBorderWidth / 2;
    TmpPart.Position.Y := FHeight / 2 + vTopBorderHeight / 2;
    TmpPart.Width := vLeftBorderWidth;
    TmpPart.Height := vBottomBorderHeight;
  end
  else if GLGUIPartLeft in FPartsInfo then
  begin
    FParts[Integer(GLGUIPartLeft)].Height := FParts[Integer(GLGUIPartLeft)].Height + vBottomBorderHeight;
    FParts[Integer(GLGUIPartLeft)].Position.Y := FParts[Integer(GLGUIPartLeft)].Position.Y + vBottomBorderHeight/2;
  end;
  if GLGUIPartBottomRight in FPartsInfo then
  begin
    TmpPart := FParts[Integer(GLGUIPartBottomRight)];
    TmpPart.Position.X := FWidth / 2 + vLeftBorderWidth / 2;
    TmpPart.Position.Y := FHeight / 2 + vTopBorderHeight / 2;
    TmpPart.Width := vRightBorderWidth;
    TmpPart.Height := vBottomBorderHeight;
    if GLGUIPartRight in FPartsInfo then
  end
  else if GLGUIPartRight in FPartsInfo then
  begin
    FParts[Integer(GLGUIPartRight)].Height := FParts[Integer(GLGUIPartRight)].Height + vBottomBorderHeight;
    FParts[Integer(GLGUIPartRight)].Position.Y := FParts[Integer(GLGUIPartRight)].Position.Y + vBottomBorderHeight/2;
  end;
end;

function TGLGUIComposite.GetWidth(): Single;
begin
  Result := FWidthOrigin;
end;

procedure TGLGUIComposite.SetWidth(const AWidth: Single);
begin
  if AWidth <> Width then
  begin
    FWidthOrigin := AWidth;
    FGUIChanges := FGUIChanges + [gcSize];
    NotifyGUIChange(Self);
  end;
end;

function TGLGUIComposite.GetHeight(): Single;
begin
  Result := FHeightOrigin;
end;

procedure TGLGUIComposite.SetHeight(const AHeight: Single);
begin
  if AHeight <> Height then
  begin
    FHeightOrigin := AHeight;
    FGUIChanges := FGUIChanges + [gcSize];
    NotifyGUIChange(Self);
  end;
end;

function TGLGUIComposite.GetScissorBottom: Integer;
var
  vY: Integer;
begin
  vY := Round(AbsolutePosition[1]);
  Result := Round(TGLGUILayer(FLayer).Height) - (vY + Round(Height * Scale.Y) div 2);
  Result := Result + Round(FBottomBorderHeight);
end;

function TGLGUIComposite.GetScissorLeft: Integer;
var
  vX: Integer;
begin
  vX := Round(AbsolutePosition[0]);
  Result := vX - Round(Width * Scale.X) div 2;
  Result := Result + Round(FLeftBorderWidth);
end;

function TGLGUIComposite.GetScissorHeight: Integer;
begin
  Result := Round(Height * Scale.Y) - Round(FTopBorderHeight + FBottomBorderHeight);
end;

function TGLGUIComposite.GetScissorWidth: Integer;
begin
  Result := Round(Width * Scale.X) - Round(FRightBorderWidth + FLeftBorderWidth);
end;

procedure TGLGUIComposite.SetAlpha(const AAlpha: Single);
var
  I: Integer;
begin
  inherited;
  for I := 0 to 8 do
    if TGLGUIPart(i) in FPartsInfo then
      FParts[i].Alpha := FAlpha;
end;

procedure TGLGUIComposite.SetColor(AColor: TGLColor);
begin
  inherited;
  UpdatePartsColor(Self);
end;

procedure TGLGUIComposite.UpdatePartsColor(Sender: TObject);
var
  I: Integer;
begin
  for I := 0 to 8 do
    if TGLGUIPart(i) in FPartsInfo then
      FParts[i].Color.DirectColor := FColor.Color;
end;

procedure TGLGUIComposite.SetSkinName(const ASkinName: String);
var
  I: Integer;
  vSkin: TGLGUICompositeSkin;
begin
  if ASkinName = FSkinName then
    Exit;

  FSkinName := ASkinName;

  FSkin := TGLGUICompositeSkin(GUISkinManager.GetSkinByName(ASkinName));

  for I := 0 to 8 do
  begin
    if TGLGUIPart(i) in FPartsInfo then
    begin
      Remove(FParts[i], False);
      FParts[i].Free;
    end;
  end;
  FPartsInfo := [];
  FPartsCount := 0;

  if FSkin.ClassType = TGLGUICompositeSkin then
  begin
    if not FSkin.IsParsed then
      FSkin.Parse;
    SetSkin(FSkin);
    SetBordersSize(FSkin.Borders);
  end
  else
    if FSkin.ClassType = TGLGUIBasicSkin then
    begin
      if not FSkin.IsParsed then
        TGLGUIBasicSkin(FSkin).Parse;
      vSkin := TGLGUICompositeSkin.Create;
      vSkin.Assign(TGLGUIBasicSkin(FSkin), True);
      FSkin := vSkin;
      SetSkin(FSkin);
    end
  else
    Exit;
end;

procedure TGLGUIComposite.SetSkin(const ASkin: TGLGUICompositeSkin);
var
  i: Integer;
begin
  for i := 0 to 8 do
  begin
    if Assigned(ASkin.Parts[i]) then
      AddPart(TGLGUIPart(i), ASkin.Parts[i]);
  end;
end;

procedure TGLGUIComposite.AddPart(const APart: TGLGUIPart; const ASkin: TGLGUIBasicSkin);
var
  vPart: TGLGUIQuad;
begin
  if not (APart in FPartsInfo) then
  begin
    vPart := TGLGUIQuad.CreateAsChild(Self);
    vPart.MoveFirst;
    vPart.SetSubComponent(True);
    vPart.EventBehaviour := ebPassEvent;
    vPart.Color := Color;
    FParts[Ord(APart)] := vPart;
    FPartsInfo := FPartsInfo + [APart];
    FPartsCount := FPartsCount + 1;
  end
  else
    vPart:= FParts[Ord(APart)];
  vPart.Alpha := FAlpha;
  TGLGUIQuadAccessProvider(vPart).FSkin := ASkin;
  TGLGUIQuadAccessProvider(vPart).SetSkin(ASkin);
end;

procedure TGLGUIComposite.SetState(const AState: TGLGUIState);
begin
  if (AState <> FCurrentState) then
  begin
    FCurrentState := AState;
    if Assigned(FSkin) then
    begin
      if AState = GLGUIEnabled then
      begin
        SetSkin(FSkin);
        Exit;
      end
      else if Assigned(FSkin.StateDescription[AState]) then
        SetSkin(FSkin.StateDescription[AState].StateSkin);
    end;

    FOnStateChange.DispatchEvent(Self);
  end;
end;

function TGLGUIComposite.GetBoreders(): TVector;
begin
  Result := VectorMake(FTopBorderHeight, FBottomBorderHeight,
    FLeftBorderWidth, FRightBorderWidth)
end;

procedure TGLGUIComposite.SetBordersSize(const ATop, ABottom, ALeft, ARight : Single);
begin
  FTopBorderHeight := ATop;
  FBottomBorderHeight := ABottom;
  FLeftBorderWidth :=  ALeft;
  FRightBorderWidth := ARight;

  NotifyGUIChange(Self);
end;

procedure TGLGUIComposite.SetBordersSize(const ASize: TVector);
begin
  SetBordersSize(ASize[0], ASize[1], ASize[2], ASize[3]);
end;

procedure TGLGUIComposite.NotifyGUIChange(Sender: TObject = nil);
begin
  inherited NotifyGUIChange(Sender);
  if FNotifyGUIChanges then
    DoOnResize;
end;

procedure TGLGUIComposite.DispatchEvent(AEvent: TGLGUIEvent; AExternal: Boolean = True);
var
  i: Integer;
begin
  for i := Count - 1 downto FPartsCount do
    if Children[i] is TGLGUIObject then
      (Children[i] as TGLGUIObject).DispatchEvent(AEvent, False);

  if FEnabled then
    AEvent.DispatchEvent(Self);
  if AExternal then
    AEvent.EventPhase := epWorking;
end;

destructor TGLGUIComposite.Destroy;
begin
  inherited;
end;

end.
